package com.googlecode.imanager.client.util;

import java.util.AbstractSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.googlecode.imanager.client.event.InputEvent;

/**
 * Provides general support to row selection routines for widgets. <br/>
 * <br/>
 * Note that this selection saves the indexes of items that are selected.<br/>
 * It's is great, since an store can have duplicated elements.
 * 
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public abstract class AbstractSelection extends AbstractSet<Integer> implements Set<Integer>, Selection {

    
    private Set<Integer> itemList;
    private SelectionMode mode = SelectionMode.EXTENDED;
    private int rangeFocused = -1;
    private int focused = -1;
    
    
    public AbstractSelection() {
        this.itemList = createList(10);
    }
    
    public AbstractSelection(int capacity) {
        this.itemList = createList(capacity);
    }
    
    public void setMode(SelectionMode mode) {
        if (mode == null){
            mode = SelectionMode.NONE;
        }
        this.mode = mode;
    }

    public SelectionMode getMode() {
        return mode;
    }
    
    protected Set<Integer> createList(int capacity) {
        return new HashSet<Integer>(capacity);
    }

    private Integer internalGetFirst() {
        if (isEmpty()){
            return null;
        }
        return itemList.iterator().next();
    }

    protected boolean internalAdd(Integer itemIndex) {
        return itemList.add(itemIndex);
    }
    
    protected boolean internalRemove(Integer itemIndex) {
        return itemList.remove(itemIndex);
    }
    
    @Override
    public boolean contains(Object o) {
        return itemList.contains(o);
    }

    @Override
    public int size() {
        return itemList.size();
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {

            private Iterator<Integer> iterator = itemList.iterator();
            private Integer curItem;
            
            @Override
            public boolean hasNext() {
                curItem = null;
                return iterator.hasNext();
            }

            @Override
            public Integer next() {
                return curItem = iterator.next();
            }

            @Override
            public void remove() {
                if (curItem == null){
                    throw new IllegalStateException();
                }
                if (onCanDeselect(curItem)){
                    iterator.remove();
                    onDeselect(curItem);
                }
            }
        };
    }
    
    /**
     * {@inheritDoc}
     * @return True if this item was been added as result of this operation
     */
    @Override
    public boolean add(Integer itemIndex) {
        if (!contains(itemIndex)) {
            if (itemIndex != null 
                    && isValidItem(itemIndex) 
                    && onCanSelect(itemIndex)){
                internalAdd(itemIndex);
                onSelect(itemIndex);
                return true;
            }
        }
        return false;
    }

    /**
     * {@inheritDoc}
     * @return True if this item was been removed as result of this operation
     */
    @Override
    public boolean remove(Object itemIndex) {
        if (contains(itemIndex)) {
            Integer i = (Integer) itemIndex;
            if (onCanDeselect(i)){
                internalRemove(i);
                onDeselect(i);
                return true;
            }
        }
        return false;
    }

    public void setFocused(int focusedItem) {
        if (this.focused != focusedItem 
                && (focusedItem == -1 || isValidItem(focusedItem))){
            
            if (this.focused != -1){
                onBlur(this.focused);
            }
            
            this.focused = focusedItem;
    
            if (this.focused != -1){
                onFocus(this.focused);
            }        
        }
    }

    public int getFocused() {
        return focused;
    }

    public void setSelected(int itemIndex){
        if (itemIndex == -1){
            clear();
            return;
        }
        if (!add(itemIndex) || size() != 1){
            for (Iterator<Integer> iterator = iterator(); iterator.hasNext();){
                if (iterator.next().equals(itemIndex)){
                    iterator.remove();
                }
            }
        }
    }
    
    public int getSelected(){
        Integer i = this.internalGetFirst();
        return i == null ? -1 : i; 
    }
    
    /*public int getEventsToSink(){
        return Event.ONCLICK | Event.ONKEYDOWN | Event.ONKEYPRESS;
    }*/
    
    /**
     * Process browser events to manage the selected items.
     * 
     * @param event the native event
     * 
     * @param itemIndex the target item to be selected
     */
    /*public void onBrowserEvent(Event event, int itemIndex) {
        int eventType = event.getTypeInt();
        
        if (eventType == Event.ONCLICK){
            processSelectionEvent(event, itemIndex);
            return;
        }
        
        if (eventType == Event.ONKEYPRESS){
            switch (event.getKeyCode()){
                
                case KeyCodes.KEY_ENTER:
                    processSelectionEvent(event, itemIndex);
                    break;
                case 32: //KeyCodes.KEY_SPACE
                    processSelectionEvent(event, itemIndex);
                    event.preventDefault();
                    break;

            }
            return;
        }
        
        if (eventType == Event.ONKEYDOWN){
            switch (event.getKeyCode()){
                
                case KeyCodes.KEY_HOME:
                    processFocusEvent(event, 0);
                    break;                    
                case KeyCodes.KEY_END:
                    processFocusEvent(event, getMaxRange()-1);
                    break;
                    
                case KeyCodes.KEY_PAGEUP:
                    processFocusEvent(event, itemIndex - getPageRange());
                    break;
                case KeyCodes.KEY_PAGEDOWN:
                    processFocusEvent(event, itemIndex + getPageRange());
                    break;
                    
                case KeyCodes.KEY_UP:
                    processFocusEvent(event, itemIndex - 1);
                    break;                    
                case KeyCodes.KEY_DOWN:
                    processFocusEvent(event, itemIndex + 1);
                    break;
            }
        }
    }*/
    
    private void processFocusEvent(InputEvent event, int itemIndex) {
        if (itemIndex < 0){
            itemIndex = 0;
        }
        if (itemIndex >= getMaxRange()){
            itemIndex = getMaxRange() - 1;
        }
        
        if (event.isShiftPressed()){
            processSelectionEvent(event, itemIndex);
        } else {
            setFocused(itemIndex);
        }
        
        event.setPreventDefault(true);
    }

    private void processSelectionEvent(InputEvent event, int itemIndex) {
        int focused = getFocused();
        int rangeFocused = this.rangeFocused;
        this.rangeFocused = -1;
        setFocused(itemIndex);

        switch (mode){
            case SINGLE: 
                if (contains(itemIndex)){
                    clear();
                } else {
                    setSelected(itemIndex);
                }
                break;
            
            case EXTENDED: 
                if (!event.isCtrlPressed() && !event.isShiftPressed()){
                    if (contains(itemIndex) && size() == 1){
                        clear();
                    }
                    setSelected(itemIndex);
                    break;
                }
            case MULTIPLE:
                if (event.isShiftPressed()){
                    if (!event.isCtrlPressed() && mode != SelectionMode.EXTENDED){
                        clear();
                    }
                    if (focused < 0){
                        focused = 0;
                    }
                    int start = focused;
                    if (rangeFocused != -1){
                        start = rangeFocused;
                    }
                    this.rangeFocused = start;
                    if (itemIndex < start){
                        int xchg = itemIndex;
                        itemIndex = start;
                        start = xchg;
                    }
                    if (start < 0){
                        start = 0;
                    }
                    for (; start<=itemIndex; start++){
                        add(start);
                    }
                }
                else {
                    if (contains(itemIndex)){
                        remove(itemIndex);
                    } else {
                        add(itemIndex);
                    }
                }
                break;
        }        
    }
    
    public abstract int getMaxRange();

    public abstract int getPageRange();

    /**
     * Checks if the item is in a valid range. <br/>
     * Note that number less than zero should always be invalid.
     * 
     * @param itemIndex
     *            the item
     * @return True if the item can be focused/added/removed in this selection
     */
    public boolean isValidItem(int itemIndex){
        return itemIndex > -1 && itemIndex < getMaxRange();
    }
    
    /**
     * This event is fired before the item be added to selection.
     * 
     * @param itemIndex
     *            the item
     * @return True if the item can be added to selection
     */
    protected boolean onCanSelect(int itemIndex) {
        return true;
    }

    /**
     * This event is fired before the item be removed from selection.
     * 
     * @param itemIndex
     *            the item
     * @return True if the item can be removed from selection
     */
    protected boolean onCanDeselect(int itemIndex) {
        return true;
    }
    
    /**
     * This event is fired after the item be added to selection.
     * 
     * @param itemIndex
     *            the item
     */
    protected void onSelect(int itemIndex) {
    }

    /**
     * This event is fired after the item be removed from selection.
     * 
     * @param itemIndex
     *            the item
     */
    protected void onDeselect(int itemIndex) {       
    }

    /**
     * This event is fired after set a new focused item.
     * 
     * @param itemIndex
     *            the new focused item
     */
    protected void onFocus(int itemIndex) {
    }

    /**
     * This event is fired before set a new focused item.
     * 
     * @param itemIndex
     *            the current focused item
     */
    protected void onBlur(int itemIndex) {       
    }

}